﻿using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using ViewMaker;
using ViewMaker.Core;
using ViewMaker.Core.Controls;
using System.Collections.Generic;
using CloudStorageLight.WpfClient.Properties;
using System.Windows.Threading;
using ViewMaker.Core.Wpf;
using System.Runtime.InteropServices;
using System.Text;
using System.Security.Cryptography;
using System.Diagnostics.CodeAnalysis;

namespace CloudStorageLight.WpfClient
{
    public class AppViewModel : ViewModelBase
    {
        /// <summary>
        /// 処理中
        /// </summary>
        public bool IsBusy
        {
            get { return _isBusy; }
            set { _isBusy = value; OnPropertyChanged("IsBusy", "IsEnabled"); }
        }
        private bool _isBusy;

        /// <summary>
        /// キャンセル中
        /// </summary>
        public bool IsCancelled
        {
            get { return _isCanceled; }
            set { _isCanceled = value; OnPropertyChanged("IsCanceled"); }
        }
        private bool _isCanceled;

        /// <summary>
        /// キャンセル可能か
        /// </summary>
        public bool CanCanceled
        {
            get { return _canCanceled; }
            set { _canCanceled = value; OnPropertyChanged("CanCanceled"); }
        }
        private bool _canCanceled;

        /// <summary>
        /// キャンセル可能か
        /// </summary>
        public bool MultiAction
        {
            get { return _multiAction; }
            set { _multiAction = value; OnPropertyChanged("MultiAction"); }
        }
        private bool _multiAction;

        /// <summary>
        /// 処理中メッセージ
        /// </summary>
        public string ProgressMessage
        {
            get { return _progressMessage; }
            set { _progressMessage = value; OnPropertyChanged("ProgressMessage"); }
        }
        private string _progressMessage;

        /// <summary>
        /// 処理結果メッセージ
        /// </summary>
        public string Message
        {
            get { return _message; }
            set { _message = value; OnPropertyChanged("Message"); }
        }
        private string _message;

        /// <summary>
        /// 処理可能か
        /// </summary>
        public bool IsEnabled
        {
            get { return !IsBusy; }
        }

        /// <summary>
        /// テスト用に非同期処理を同期的に行うように動作
        /// </summary>
        public static bool ExecuteActionAsyncMode = true;

        /// <summary>
        /// アクションを実行するテンプレートメソッド
        /// </summary>
        /// <param name="task"></param>
        /// <param name="callback"></param>
        /// <param name="message"></param>
        /// <param name="canCancel"></param>
        /// <param name="isBusy"></param>
        [ExcludeFromCodeCoverage]
        protected void ExecuteActionAsync(Task task, Action<Task> callback = null, string message = null, bool canCancel = false, bool isBusy = true, bool multiAction = false)
        {

            CanCanceled = canCancel;
            IsCancelled = false;
            IsBusy = isBusy;
            ProgressMessage = message ?? Resources.MsgProcessing;
            Message = "";
            MultiAction = multiAction;

            if (ExecuteActionAsyncMode)
            {
                var uiFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());
                task.ContinueWith((x) =>
                {
                    uiFactory.StartNew(() =>
                    {
                        if (callback != null) callback(x);
                        if (x.IsFaulted)
                        {
                            Debug.WriteLine(x.Exception.ToString());
                            if (x.Exception.InnerException != null)
                            {
                                this.ShowError(x.Exception.InnerException);
                                Message = x.Exception.InnerException.Message;
                            }
                            else
                            {
                                this.ShowError(x.Exception);
                                Message = x.Exception.Message;
                            }
                        }
                        else
                        {
                            ProgressMessage = "";
                        }
                        IsBusy = false;
                    });
                });
            }
            else
            {
                try
                {
                    task.Wait();
                    if (callback != null) callback(task);
                    ProgressMessage = "";
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.ToString());
                    if (ex.InnerException != null)
                    {
                        Message = ex.InnerException.Message;
                    }
                    else
                    {
                        Message = ex.Message;
                    } 
                }
                IsBusy = false;

            }


        }

        private object syncObject = new object();

        protected void ExecuteUIProc(Action action)
        {
            if (Application.Current == null || Application.Current.Dispatcher.CheckAccess())
            {
                lock(syncObject)
                {
                    action();
                }
           }
            else
            {
                Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
                {
                    action();
                }));
            }

        }

        public void DoEvents()
        {
            DispatcherFrame frame = new DispatcherFrame();
            Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
                new DispatcherOperationCallback(ExitFrames), frame);
            Dispatcher.PushFrame(frame);
        }

        public object ExitFrames(object f)
        {
            ((DispatcherFrame)f).Continue = false;

            return null;
        }

        [ExcludeFromCodeCoverage]
        protected virtual MessageBoxResult ShowYesNoCancel(string message, string title = "confirm")
        {
            return MessageBox.Show(message, title, MessageBoxButton.YesNoCancel);
        }


        /// <summary>
        /// ファイルを表示・編集するメインフロー
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected async Task ExecuteFile(FileItem item, bool editing)
        {
            if (item == null) return;
            var isReadonly = item.IsReadOnly;
            try
            {
                item.Downloading = true;

                var cache = BlobDataManager.Instance.GetCachedFile(item) ?? BlobDataManager.Instance.CreateCacheFileEntry(item);
                var downloaded = cache.IsDownloaded;

                if (!BlobDataManager.Instance.Offline)
                {
                    var svrItem = (FileItem)await BlobDataManager.Instance.GetFileAsync(item.Path);
                    if (svrItem == null) throw new ApplicationException(Resources.MsgNotFound);
                    item.LastModified = svrItem.LastModified;
                    item.CheckoutUser = svrItem.CheckoutUser;
                    item.AttachmentFileName = svrItem.AttachmentFileName;
                }

                if (editing && !BlobDataManager.Instance.Offline)
                {
                    if (item == null || !item.IsCheckout || item.IsReadOnly)
                    {
                        throw new ApplicationException(Resources.MsgEditingError);
                    }
                    var serverDirty = item.LastModified != cache.LastModified;
                    if (cache.IsDirty && serverDirty)
                    {
                        var confirm = this.ShowYesNoCancel(string.Format(Resources.MsgOverwriteEdtingFile, item.Title));
                        if (confirm == MessageBoxResult.Cancel) return;
                        if (confirm == MessageBoxResult.No)
                        {
                            //サーバ変更を無視
                            serverDirty = false;
                        }
                    }
                    await ExecuteAsync(item, cache, false, serverDirty || !downloaded);

                }
                else
                {
                    if (!item.IsCheckout) isReadonly = true;
                    var serverDirty = item.LastModified != cache.LastModified;
                    await ExecuteAsync(item, cache, isReadonly, !downloaded || serverDirty);

                }
            }
            finally
            {
                item.Downloading = false;
            }
        }

        /// <summary>
        /// 指定したファイルアイテムの表示・編集処理
        /// </summary>
        /// <param name="item"></param>
        /// <param name="isReadonly">読み取り専用でオープン</param>
        /// <param name="withDownload">ダウンロード指示</param>
        /// <returns></returns>
        private async Task<bool> ExecuteAsync(FileItem item, CachedFile cachedFile, bool isReadonly, bool withDownload)
        {
            var path = item.Path;
            //var cacheId = item.Path + item.Snapshot ?? "" + "__" + item.FileProtectedMode;
            var localpath = cachedFile.LocalPath;
            var snapshot = item.Snapshot;
            var exec = FileExtentionInfo(System.IO.Path.GetExtension(localpath));
            if (withDownload)
            {
                var progress = new Progress<double>();
                progress.ProgressChanged += (s, e) =>
                {
                    ProgressMessage = string.Format(Resources.MsgDownloadFile, System.IO.Path.GetFileName(path)) + string.Format(" {0:0}%", e);
                };

                var info = await BlobDataManager.Instance.DownloadAsync(path, localpath, null, item.DownloadMode, progress, item.Length);
                if (info.LastWriteTime.HasValue) item.LastModified = info.LastWriteTime.Value;
                BlobDataManager.Instance.SetCacheDownloaded(item, info);
            }

            if (isReadonly)
            {
                SetReadOnly(localpath);
            }
            else
            {
                SetReadWrite(localpath);
            }

            var proc = ProcessStart(localpath);
            cachedFile.LastAccessTime = DateTime.Now;
            BlobDataManager.Instance.SaveCachedFile();
            return true;
        }

        public static bool ProcessStartMode = true;

        public string LastProcessStartCmd { get; set; }

        protected virtual Process ProcessStart(string cmd)
        {
            LastProcessStartCmd = cmd;

            if (ProcessStartMode)
            {
                return Process.Start(cmd);
            }
            else
            {
                return null;
            }
        }
        public ICommand CancelCommand
        {
            get
            {
                return CreateCommand(() => {
                    IsCancelled = true;
                    CanCanceled = false;
                    ProgressMessage = Resources.MsgCanceling;
                    BlobDataManager.Instance.Cancel();
                });
            }
        }

        [ExcludeFromCodeCoverage]
        protected virtual bool ShowError(Exception ex)
        {
            Trace.WriteLine(ex.ToString());
            var cancel = ex as TaskCanceledException;
            if (cancel != null) return false;
            if (ex.InnerException != null)
            {
                cancel = ex.InnerException as TaskCanceledException;
                if (cancel != null) return false;
                ex = ex.InnerException;
            }
            Exception web = ex as System.Net.WebException;
            if (web == null) web = ex as System.Net.Http.HttpRequestException;
            if (web == null && ex.InnerException != null)
            {
                web = ex.InnerException as System.Net.WebException;
                if (web == null) web = ex as System.Net.Http.HttpRequestException;
            }
            if (web != null)
            {
                var message = string.Format(Resources.MsgNetError, web.Message);
                ex = new ApplicationException(message);
            }

            return ShowError(ex.Message, !(ex is ApplicationException));
        }
        [ExcludeFromCodeCoverage]
        protected virtual bool ShowError(string message, bool systemError)
        {
            var ret = true;
            var dispErrorMessage = systemError ? string.Format(Resources.MsgErrorDisplayAndConfirm, message) : string.Format(Resources.MsgAppErrorDisplayAndConfirm, message);
            if (!MultiAction)
            {
                dispErrorMessage = systemError ? string.Format(Resources.MsgErrorDisplay, message) : string.Format(Resources.MsgAppErrorDisplay, message);
            }

            if (Application.Current == null || Application.Current.Dispatcher.CheckAccess())
            {
                Message = message;
                if (MultiAction)
                {
                    if (!this.ShowOKCancel(dispErrorMessage, "ERROR"))
                    {
                        CancelCommand.Execute(null);
                        ret = false;
                    }
                }
                else
                {
                    this.ShowOKCancel(dispErrorMessage, "ERROR");
                    ret = false;
                }
            }
            else
            {
                Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() =>
                {
                    Message = message;
                    if (MultiAction)
                    {
                        if (!this.ShowOKCancel(dispErrorMessage, "ERROR"))
                        {
                            CancelCommand.Execute(null);
                            ret = false;
                        }
                    }
                    else
                    {
                        this.ShowOKCancel(dispErrorMessage, "ERROR");
                        ret = false;
                    }
                }));
            }
            return ret;
        }

        protected virtual bool ShowOKCancel(string message, string title="confirm")
        {
            return ViewModelExtension.ShowOKCancel(this,message, title);
        }

        #region internal utility

        protected void SetReadWrite(string name)
        {
            var attr = File.GetAttributes(name);
            attr &= ~FileAttributes.ReadOnly;
            File.SetAttributes(name, attr);
        }

        protected void SetReadOnly(string name)
        {
            var attr = File.GetAttributes(name);
            attr |= FileAttributes.ReadOnly;
            File.SetAttributes(name, attr);
        }

        public string FileExtentionInfo(string doctype)
        {
            return Utils.FileExtentionInfo(Utils.AssocStr.Executable, doctype);
        }


        #endregion
    }

    
}
